#include "parser.hpp"

#include "tokens.hpp"
#include "token_sets.hpp"
#include "lexer.hpp"
#include "AST.hpp"

#include <iostream>
#include <vector>
#include <list>
#include <string>
#include <memory>

using namespace std;

Parser::Parser( istream& input ) : lexer( input )
{
	token = lexer.peek();
	if ( token == Tkn::InvalidToken )
	{
		reportInvalidToken();
		throw "shit";
	}
}

void Parser::consume()
{
	lexer.consume();
	token = lexer.peek();
	if ( token == Tkn::InvalidToken )
	{
		reportInvalidToken();
		throw "shit";
	}
}

void Parser::match( Tkn::Token token )
{
	Tkn::Token currentToken = lexer.peek();
	if ( currentToken == token )
	{
		consume();
	}else
	{
		reportUnexpectedToken( token );
		throw "shit";
	}
}

ASTNode::aptr Parser::parseCall( ASTNode::aptr callingExpression )
{
	match( Tkn::LParen );
	
	CallAST::aptr call( new CallAST() );
	call->object = callingExpression;
	
	if ( token != Tkn::RParen )
	while(true)
	{
		call->parameters.push_back( parseExpression().release() );
		if ( token != Tkn::Comma ) break;
		else consume();
	}
	
	match( Tkn::RParen );
	
	return ASTNode::aptr(call);
}

ASTNode::aptr Parser::parseIdentifierOrSpecialization()
{
	ASTNode::aptr node;
	string name = lexer.getTokenContent();
	match(Tkn::Identifier);
	if ( token == Tkn::DotBracket )
	{
		node = parseTemplateSpecialization( name );
	}else 
	{
		node = ASTNode::aptr( new IdentifierAST( name ) );
	}
	return node;
}

ASTNode::aptr Parser::parseType()
{
	ASTNode::aptr type = parseIdentifierOrSpecialization();
	
	while ( token == Tkn::Dot )
	{
		consume();
		ASTNode::aptr member = parseIdentifierOrSpecialization();
		MemberAccessAST::aptr memberAccess( new MemberAccessAST() );
		memberAccess->memberName = member;
		memberAccess->object = type;
		type = ASTNode::aptr(memberAccess);
	}
	
	return type;
}

ASTNode::aptr Parser::parseVariableDeclaration()
{
	match( Tkn::Var );
	
	VariableDeclarationAST::aptr varDec( new VariableDeclarationAST() );
	varDec->name = lexer.getTokenContent();
	match( Tkn::Identifier );
	
	bool typeDeclared = false;
	if ( token == Tkn::Colon )
	{
		consume();
		varDec->type = parseType();
		typeDeclared = true;
	}
	
	if ( token == Tkn::Assign )
	{
		consume();
		varDec->initialization = parseExpression();
	}else if ( !typeDeclared )
	{
		reportUnexpectedToken( "Type declaration or Assignment" );
	}
	
	return ASTNode::aptr(varDec);
}

ASTNode::aptr Parser::parseTemplateSpecialization( const string& name )
{
	match( Tkn::DotBracket );
	
	TemplateSpecializationAST::aptr TS( new TemplateSpecializationAST() );
	
	TS->name = name;
	
	while (true)
	{
		if ( token == Tkn::Numeral )
		{
			consume();
			TS->parameters.push_back( parseExpression().release() );
		}else
		{
			TS->parameters.push_back( parseType().release() );
		}
		
		if ( token != Tkn::Comma ) break;
		consume();
	}
	
	match( Tkn::RBracket );
	
	return ASTNode::aptr(TS);
}

ASTNode::aptr Parser::parsePrimaryExpression()
{
	if ( !tsets.PrimaryExpression[token] )
	{
		reportUnexpectedToken( "Primary Expression" );
		throw "shit";
	}
	
	ASTNode::aptr primaryExpr;
	
	if ( tsets.UnaryOperator[token] )
	{
		UnaryOpAST::aptr unaryOp( new UnaryOpAST() );
		unaryOp->op = token;
		consume();
		unaryOp->argument = parsePrimaryExpression();
		return ASTNode::aptr(unaryOp);
	}else if ( token == Tkn::If )
	{
		return parseIf();
	}
	
	if ( token == Tkn::LParen )
	{
		consume();
		primaryExpr = parseExpression();
		match( Tkn::RParen );
	}else if ( token == Tkn::Identifier )
	{
		primaryExpr = parseIdentifierOrSpecialization();
	}else if ( tsets.Value[token] )
	{
		primaryExpr = ASTNode::aptr(0);
		consume();
	}
	
	while ( true )
	{
		if ( token == Tkn::Dot )
		{
			consume();
			ASTNode::aptr memberName = parseIdentifierOrSpecialization();
			MemberAccessAST::aptr memberAccess( new MemberAccessAST() );
			memberAccess->object = primaryExpr;
			memberAccess->memberName = memberName;
			primaryExpr = memberAccess;
		}else if ( token == Tkn::LParen )
		{
			primaryExpr = parseCall( primaryExpr );
		}else
		{
			break;
		}
	}
	
	return primaryExpr;
}

ASTNode::aptr Parser::parseExpression()
{
	ASTNode::aptr leftSide = parsePrimaryExpression();
	
	if ( tsets.BinaryOperator[token] )
	{
		consume();
		parseExpression();
	}
	
	return leftSide;
}

ASTNode::aptr Parser::parseStatement()
{
	if ( !tsets.Statement[token] )
	{
		reportUnexpectedToken("Statement");
		throw "shit";
	}
	
	ASTNode::aptr statement;

	if ( token == Tkn::LCurlyBrace ) return ASTNode::aptr( parseCodeBlock() );
	if ( tsets.Expression[token] )
	{
		statement = parseExpression();
	}else if ( token == Tkn::Var )
	{
		statement = parseVariableDeclaration();
	}else if ( token == Tkn::Do )
	{
		statement = parseDoWhile();
	}else if ( token == Tkn::While )
	{
		statement = parseWhile();
	}else if ( token == Tkn::Return )
	{
		consume();
		if ( tsets.Expression[token] )
		{
			parseExpression();
		}
	}
	
	return statement;
}

ASTNode::aptr Parser::parseIf()
{
	IfAST::aptr ifNode( new IfAST() );
	
	match( Tkn::If );
	match( Tkn::LParen );
	
	ifNode->condition = parseExpression();
	
	match( Tkn::RParen );
	
	ifNode->body1 = parseStatement();
	
	if ( token == Tkn::Else )
	{
		consume();
		ifNode->body2 = parseStatement();
	}
	
	return ASTNode::aptr(ifNode);
}

ASTNode::aptr Parser::parseWhile()
{
	WhileAST::aptr whileNode( new WhileAST() );
	
	match( Tkn::While );
	match( Tkn::LParen );
	whileNode->condition = parseExpression();
	match( Tkn::RParen );
	whileNode->body = parseStatement();
	return ASTNode::aptr(whileNode);
}

ASTNode::aptr Parser::parseDoWhile()
{
	DoWhileAST::aptr whileNode( new DoWhileAST() );
	
	match( Tkn::Do );
	whileNode->body = parseStatement();
	match( Tkn::While );
	match( Tkn::LParen );
	whileNode->condition = parseExpression();
	match( Tkn::RParen );
	return ASTNode::aptr(whileNode);
}

ASTNode::aptr Parser::parseCodeBlock()
{
	CodeBlockAST::aptr block( new CodeBlockAST() );
	
	match( Tkn::LCurlyBrace );
	
	while ( true )
	{
		if ( tsets.Statement[token] ) // Token belongs to the set of Statement
		{
			block->statements.push_back( parseStatement().release() );
		}
		
		if ( token == Tkn::RCurlyBrace ) break;
		else if ( token == Tkn::Semicolon )
		{
			consume(); continue;
		}else
		{
			reportUnexpectedToken( "} or ;" );
			throw "shit";
		}
	}
	
	match( Tkn::RCurlyBrace );
	
	return ASTNode::aptr(block);
}

ASTNode::aptr Parser::parseFunction()
{
	FunctionAST::aptr function( new FunctionAST() );
	
	consume();
	
	function->name = lexer.getTokenContent();
	match( Tkn::Identifier );
	
	match( Tkn::LParen );
	
	bool defaultValueDeclared = false;
	if ( token != Tkn::RParen )
	while(true)
	{
		auto_ptr<FunctionAST::Parameter> param( new FunctionAST::Parameter() );
		
		param->name = lexer.getTokenContent();
		match( Tkn::Identifier );
		
		match( Tkn::Colon );
		
		param->type = parseType();
		
		if ( token == Tkn::Assign || defaultValueDeclared )
		{
			match( Tkn::Assign );
			param->defaultValue = parseExpression();
			defaultValueDeclared = true;
		}
		
		function->parameters.push_back( param.release() );
		
		if ( token != Tkn::Comma ) break;
		else consume();
	}
	
	match( Tkn::RParen );
	
	if ( token == Tkn::Colon )
	{
		consume();
		function->type = parseType();
	}
	
	function->body = parseCodeBlock();
	return ASTNode::aptr(function);
}

ASTNode::aptr Parser::parseModule()
{
	ModuleAST::aptr module( new ModuleAST() );
	while ( true ) {
		switch ( token )
		{
			case Tkn::Function: {
				ASTNode::aptr func = parseFunction();
				module->functions.push_back( func.release() );
			} break;
			case Tkn::Class: {
				//ClassAST* func = parseClass( lexer );
				cout << "a CLASS LOL" << endl;
				consume();
			} break;
			case Tkn::EOI: {
				cout << "NO ERRORS LOL" << endl;
				return ASTNode::aptr(module);
			} break;
			default:
				reportUnexpectedToken( "function or class" );
				throw "shit";
			break;
		}
	}
	return ASTNode::aptr(module);
}

ASTNode::aptr Parser::parse( istream& input )
{
	try {
		Parser parser( input );
		return parser.parseModule();
	} catch (...)
	{
		//cout << "Error: " << str << endl;
		cout << "Things ended badly :(" << endl;
		return ASTNode::aptr();
	}
}

// ******** Errors **********

void Parser::reportError( const string& error ) {
	cout << "Line " << lexer.getLineNumber() << ": "<< error << endl;
}

void Parser::reportInvalidToken() {
	reportError( "Invalid token: " + lexer.getTokenContent() );
}

void Parser::reportUnexpectedToken() {
	reportError( "Unexpected token: " + lexer.getTokenContent() );
}

void Parser::reportUnexpectedToken( Tkn::Token expected ) {
	reportError( "Unexpected token: " + lexer.getTokenContent() + " , when expecting: " + Tkn::name(expected) );
}

void Parser::reportUnexpectedToken( const std::string& expected ) {
	reportError( "Unexpected token: " + lexer.getTokenContent() + " , when expecting: " + expected );
}

